(module interp (lib "eopl.ss" "eopl")
  
  (require "drscheme-init.scm")

  (require "lang.scm")
  (require "data-structures.scm")
  (require "environments.scm")

  (provide value-of-program value-of)

;;;;;;;;;;;;;;;; the interpreter ;;;;;;;;;;;;;;;;

  ;; value-of-program : program -> expval

  (define value-of-program 
    (lambda (pgm)
      (cases program pgm
        (a-program (body)
          (value-of body (init-env))))))

  ;; value-of : expression * environment -> expval

  (define value-of
    (lambda (exp env)
      (cases expression exp

        (const-exp (num) (num-val num))

        (var-exp (id) (apply-env env id))

        (diff-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (num-val
	      (- val1 val2))))
        (plus-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (num-val
	      (+ val1 val2))))

        (times-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (num-val
	      (* val1 val2))))

        (div-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (num-val
	      (quotient val1 val2))))

        (equal-test-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (bool-val
	      (= val1 val2))))
        
        
        (greater-test-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (bool-val
	      (> val1 val2))))
        
                
        (less-test-exp (exp1 exp2)
          (let ((val1
		  (expval->num
		    (value-of exp1 env)))
                (val2
		  (expval->num
		    (value-of exp2 env))))
            (bool-val
	      (< val1 val2))))

        (not-exp (exp) 
          (if (expval->bool (value-of exp env))
            (bool-val #f)
	    (bool-val #t)))        

        ;;cond operator
        (cond-exp (lst1 lst2)
                  (if (null? lst1) 
                      (num-val 0) 
                      ( if (expval->bool (value-of (car lst1) env))
                           (value-of (car lst2) env)
                           (value-of (cond-exp  (cdr lst1)  (cdr lst2)) env)
                           )
                      )
                  )

        ;;all-of operator
        (all-of-exp (exp lst) 
                    (if (null? lst)
                        (bool-val (expval->bool (value-of exp env)))
                        ( if (not (expval->bool (value-of exp env)))
                             (bool-val #f)
                             (value-of (all-of-exp  (car lst)  (cdr lst)) env)
                             )
                        )
                    )
        
        (zero?-exp (exp1)
	  (let ((val1 (expval->num (value-of exp1 env))))
	    (if (zero? val1)
	      (bool-val #t)
	      (bool-val #f))))

        (if-exp (exp0 exp1 exp2)
          (if (expval->bool (value-of exp0 env))
            (value-of exp1 env)
            (value-of exp2 env)))

        (let-exp (id rhs body)
          (let ((val (value-of rhs env)))
            (value-of body
              (extend-env id val env))))

        (proc-exp (bvar body)
                  (proc-val
                   (procedure bvar body env)))
        
        (pair-exp (exp1 exp2)
                  (let ((val1 (value-of exp1 env))
                        (val2 (value-of exp2 env)))
                    (pair-gen val1 val2)
                    )
                  )

        (unpair-exp (exp id1 id2 in-expression)
                    (cases expval (value-of exp env)
                      (pair-gen (val1 val2)
                                (let ((vl1 val1)
                                      (vl2 val2))
                                  (value-of in-expression
                                            (extend-env id1 vl1
                                                        (extend-env id2 vl2 env)))))
                      (else (eopl:error "mistake"))))
        
        
        (app-exp (rator rand)
                 (let ((proc (expval->proc (value-of rator env)))
                       (arg  (value-of rand  env)))
	    (apply-procedure proc arg)
                   )))))
  
  ;; procedure : vble * expression * env -> proc
  
  (define procedure
    (lambda (bvar body env)
      (lambda (arg)
        (value-of body (extend-env bvar arg env)))))
  
  ;; apply-procedure : procedure * expval -> expval
  
  (define apply-procedure
    (lambda (proc arg)
      (proc arg))
  
  )
  )